/*
 * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.engine.test.api.event;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.delegate.event.ActivitiEventDispatcher;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.BaseEntityEventListener;
import org.activiti.engine.delegate.event.impl.ActivitiEntityEventImpl;
import org.activiti.engine.delegate.event.impl.ActivitiEventDispatcherImpl;
import org.activiti.engine.delegate.event.impl.ActivitiEventImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.task.Task;

/**
 *
 */
public abstract class ActivitiEventDispatcherTest extends PluggableActivitiTestCase {

    protected ActivitiEventDispatcher dispatcher;

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        dispatcher = new ActivitiEventDispatcherImpl();
    }

    /**
     * Test adding a listener and check if events are sent to it. Also checks that after removal, no events are received.
     */
    public void addAndRemoveEventListenerAllEvents() throws Exception {
        // Create a listener that just adds the events to a list
        TestActivitiEventListener newListener = new TestActivitiEventListener();

        // Add event-listener to dispatcher
        dispatcher.addEventListener(newListener);

        ActivitiEntityEventImpl event1 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_CREATED
        );
        ActivitiEntityEventImpl event2 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_CREATED
        );

        // Dispatch events
        dispatcher.dispatchEvent(event1);
        dispatcher.dispatchEvent(event2);

        assertThat(newListener.getEventsReceived()).hasSize(2);
        assertThat(newListener.getEventsReceived().get(0)).isEqualTo(event1);
        assertThat(newListener.getEventsReceived().get(1)).isEqualTo(event2);

        // Remove listener and dispatch events again, listener should not be
        // invoked
        dispatcher.removeEventListener(newListener);
        newListener.clearEventsReceived();
        dispatcher.dispatchEvent(event1);
        dispatcher.dispatchEvent(event2);

        assertThat(newListener.getEventsReceived().isEmpty()).isTrue();
    }

    /**
     * Test adding a listener and check if events are sent to it, for the types it was registered for. Also checks that after removal, no events are received.
     */
    public void addAndRemoveEventListenerTyped() throws Exception {
        // Create a listener that just adds the events to a list
        TestActivitiEventListener newListener = new TestActivitiEventListener();

        // Add event-listener to dispatcher
        dispatcher.addEventListener(newListener, ActivitiEventType.ENTITY_CREATED, ActivitiEventType.ENTITY_DELETED);

        ActivitiEntityEventImpl event1 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_CREATED
        );
        ActivitiEntityEventImpl event2 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_DELETED
        );
        ActivitiEntityEventImpl event3 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_UPDATED
        );

        // Dispatch events, only 2 out of 3 should have entered the listener
        dispatcher.dispatchEvent(event1);
        dispatcher.dispatchEvent(event2);
        dispatcher.dispatchEvent(event3);

        assertThat(newListener.getEventsReceived()).hasSize(2);
        assertThat(newListener.getEventsReceived().get(0)).isEqualTo(event1);
        assertThat(newListener.getEventsReceived().get(1)).isEqualTo(event2);

        // Remove listener and dispatch events again, listener should not be
        // invoked
        dispatcher.removeEventListener(newListener);
        newListener.clearEventsReceived();
        dispatcher.dispatchEvent(event1);
        dispatcher.dispatchEvent(event2);

        assertThat(newListener.getEventsReceived().isEmpty()).isTrue();
    }

    /**
     * Test that adding a listener with a null-type is never called.
     */
    public void addAndRemoveEventListenerTypedNullType() throws Exception {
        // Create a listener that just adds the events to a list
        TestActivitiEventListener newListener = new TestActivitiEventListener();

        // Add event-listener to dispatcher
        dispatcher.addEventListener(newListener, (ActivitiEventType) null);

        ActivitiEntityEventImpl event1 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_CREATED
        );
        ActivitiEntityEventImpl event2 = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_DELETED
        );

        // Dispatch events, all should have entered the listener
        dispatcher.dispatchEvent(event1);
        dispatcher.dispatchEvent(event2);

        assertThat(newListener.getEventsReceived().isEmpty()).isTrue();
    }

    /**
     * Test the {@link BaseEntityEventListener} shipped with Activiti.
     */
    public void baseEntityEventListener() throws Exception {
        TestBaseEntityEventListener listener = new TestBaseEntityEventListener();

        dispatcher.addEventListener(listener);

        ActivitiEntityEventImpl createEvent = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_CREATED
        );
        ActivitiEntityEventImpl deleteEvent = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_DELETED
        );
        ActivitiEntityEventImpl updateEvent = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.ENTITY_UPDATED
        );
        ActivitiEntityEventImpl otherEvent = new ActivitiEntityEventImpl(
            processEngineConfiguration.getTaskEntityManager().create(),
            ActivitiEventType.CUSTOM
        );

        // Dispatch create event
        dispatcher.dispatchEvent(createEvent);
        assertThat(listener.isCreateReceived()).isTrue();
        assertThat(listener.isUpdateReceived()).isFalse();
        assertThat(listener.isCustomReceived()).isFalse();
        assertThat(listener.isInitializeReceived()).isFalse();
        assertThat(listener.isDeleteReceived()).isFalse();
        listener.reset();

        // Dispatch update event
        dispatcher.dispatchEvent(updateEvent);
        assertThat(listener.isUpdateReceived()).isTrue();
        assertThat(listener.isCreateReceived()).isFalse();
        assertThat(listener.isCustomReceived()).isFalse();
        assertThat(listener.isDeleteReceived()).isFalse();
        listener.reset();

        // Dispatch delete event
        dispatcher.dispatchEvent(deleteEvent);
        assertThat(listener.isDeleteReceived()).isTrue();
        assertThat(listener.isCreateReceived()).isFalse();
        assertThat(listener.isCustomReceived()).isFalse();
        assertThat(listener.isUpdateReceived()).isFalse();
        listener.reset();

        // Dispatch other event
        dispatcher.dispatchEvent(otherEvent);
        assertThat(listener.isCustomReceived()).isTrue();
        assertThat(listener.isCreateReceived()).isFalse();
        assertThat(listener.isUpdateReceived()).isFalse();
        assertThat(listener.isDeleteReceived()).isFalse();
        listener.reset();

        // Test typed entity-listener
        listener = new TestBaseEntityEventListener(Task.class);

        // Dispatch event for a task, should be received
        dispatcher.addEventListener(listener);
        dispatcher.dispatchEvent(createEvent);

        assertThat(listener.isCreateReceived()).isTrue();
        listener.reset();

        // Dispatch event for a execution, should NOT be received
        ActivitiEntityEventImpl createEventForExecution = new ActivitiEntityEventImpl(
            new ExecutionEntityImpl(),
            ActivitiEventType.ENTITY_CREATED
        );

        dispatcher.dispatchEvent(createEventForExecution);
        assertThat(listener.isCreateReceived()).isFalse();
    }

    /**
     * Test dispatching behavior when an exception occurs in the listener
     */
    public void exceptionInListener() throws Exception {
        // Create listener that doesn't force the dispatching to fail
        TestExceptionActivitiEventListener listener = new TestExceptionActivitiEventListener(false);
        TestActivitiEventListener secondListener = new TestActivitiEventListener();

        dispatcher.addEventListener(listener);
        dispatcher.addEventListener(secondListener);

        ActivitiEventImpl event = new ActivitiEventImpl(ActivitiEventType.ENTITY_CREATED);
        try {
            dispatcher.dispatchEvent(event);
            assertThat(secondListener.getEventsReceived()).hasSize(1);
        } catch (Throwable t) {
            fail("No exception expected");
        }

        // Remove listeners
        dispatcher.removeEventListener(listener);
        dispatcher.removeEventListener(secondListener);

        // Create listener that forces the dispatching to fail
        listener = new TestExceptionActivitiEventListener(true);
        secondListener = new TestActivitiEventListener();
        dispatcher.addEventListener(listener);
        dispatcher.addEventListener(secondListener);

        assertThatExceptionOfType(ActivitiException.class)
            .isThrownBy(() -> dispatcher.dispatchEvent(event))
            .withCauseInstanceOf(RuntimeException.class)
            .satisfies(ae -> assertThat(ae.getCause()).hasMessage("Test exception"));

        // Second listener should NOT have been called
        assertThat(secondListener.getEventsReceived()).hasSize(0);
    }

    /**
     * Test conversion of string-value (and list) in list of {@link ActivitiEventType}s, used in configuration of process-engine
     * {@link ProcessEngineConfigurationImpl#setTypedEventListeners(java.util.Map)} .
     */
    public void activitiEventTypeParsing() throws Exception {
        // Check with empty null
        ActivitiEventType[] types = ActivitiEventType.getTypesFromString(null);
        assertThat(types).isNotNull();
        assertThat(types.length).isEqualTo(0);

        // Check with empty string
        types = ActivitiEventType.getTypesFromString("");
        assertThat(types).isNotNull();
        assertThat(types.length).isEqualTo(0);

        // Single value
        types = ActivitiEventType.getTypesFromString("ENTITY_CREATED");
        assertThat(types).isNotNull();
        assertThat(types.length).isEqualTo(1);
        assertThat(types[0]).isEqualTo(ActivitiEventType.ENTITY_CREATED);

        // Multiple value
        types = ActivitiEventType.getTypesFromString("ENTITY_CREATED,ENTITY_DELETED");
        assertThat(types).isNotNull();
        assertThat(types.length).isEqualTo(2);
        assertThat(types[0]).isEqualTo(ActivitiEventType.ENTITY_CREATED);
        assertThat(types[1]).isEqualTo(ActivitiEventType.ENTITY_DELETED);

        // Additional separators should be ignored
        types = ActivitiEventType.getTypesFromString(",ENTITY_CREATED,,ENTITY_DELETED,,,");
        assertThat(types).isNotNull();
        assertThat(types.length).isEqualTo(2);
        assertThat(types[0]).isEqualTo(ActivitiEventType.ENTITY_CREATED);
        assertThat(types[1]).isEqualTo(ActivitiEventType.ENTITY_DELETED);

        // Invalid type name
        assertThatExceptionOfType(ActivitiIllegalArgumentException.class)
            .isThrownBy(() -> ActivitiEventType.getTypesFromString("WHOOPS,ENTITY_DELETED"))
            .withMessage("Invalid event-type: WHOOPS");
    }
}
